{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Metaprogramming - Application 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's say we have some `.ini` files that hold various application configurations. We want to read those `.ini` files into an object structure so that we can access the data in our config files using dot notation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start by creating some `.ini` files:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('prod.ini', 'w') as prod, open('dev.ini', 'w') as dev:\n",
    "    prod.write('[Database]\\n')\n",
    "    prod.write('db_host=prod.mynetwork.com\\n')\n",
    "    prod.write('db_name=my_database\\n')\n",
    "    prod.write('\\n[Server]\\n')\n",
    "    prod.write('port=8080\\n')\n",
    "    \n",
    "    dev.write('[Database]\\n')\n",
    "    dev.write('db_host=dev.mynetwork.com\\n')\n",
    "    dev.write('db_name=my_database\\n')\n",
    "    dev.write('\\n[Server]\\n')\n",
    "    dev.write('port=3000\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: I could have used the `configparser` module to write out these ini files, but we don't have to - generally these config files are created and edited manually. We will use `configparser` to load up the config files though."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we start our program, we want to load up one of these files into a config object of some sort.\n",
    "\n",
    "We could certainly do it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import configparser\n",
    "\n",
    "class Config:\n",
    "    def __init__(self, env='dev'):\n",
    "        print(f'Loading config from {env} file...')\n",
    "        config = configparser.ConfigParser()\n",
    "        file_name = f'{env}.ini'\n",
    "        config.read(file_name)\n",
    "        self.db_host = config['Database']['db_host']\n",
    "        self.db_name = config['Database']['db_name']\n",
    "        self.port = config['Server']['port']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loading config from dev file...\n"
     ]
    }
   ],
   "source": [
    "config = Config('dev')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'db_host': 'dev.mynetwork.com', 'db_name': 'my_database', 'port': '3000'}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config.__dict__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "but whenever we need access to this config object again, we either have to store the object somewhere in a global variable (common, and extremely simple!), or we need to re-create it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loading config from dev file...\n"
     ]
    }
   ],
   "source": [
    "config = Config('dev')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Which means we end up parsing the `ini` file over and over again."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'my_database'"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config.db_name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on Config in module __main__ object:\n",
      "\n",
      "class Config(builtins.object)\n",
      " |  Config(env='dev')\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, env='dev')\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Furthermore, `help` is not very useful to us here."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The other thing is that we had to \"hardcode\" each config value in our `Config` class. \n",
    "\n",
    "That's a bit of a pain. \n",
    "\n",
    "Could we maybe create instance attributes from inspecting what's inside the `ini` files instead?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Config:\n",
    "    def __init__(self, env='dev'):\n",
    "        print(f'Loading config from {env} file...')\n",
    "        config = configparser.ConfigParser()\n",
    "        file_name = f'{env}.ini'\n",
    "        config.read(file_name)\n",
    "        for section_name in config.sections():\n",
    "            for key, value in config[section_name].items():\n",
    "                setattr(self, key, value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loading config from prod file...\n"
     ]
    }
   ],
   "source": [
    "config = Config('prod')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'db_host': 'prod.mynetwork.com', 'db_name': 'my_database', 'port': '8080'}"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config.__dict__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So this is good, we can access our config values using dot notation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'8080'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config.port"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next issue we need to deal with is that our config files are organized into sections, and here we've essentially ignored this and create just a \"flat\" data structure."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So let's deal with that next."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's write a custom class for representing sections:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Section:\n",
    "    def __init__(self, name, item_dict):\n",
    "        \"\"\"\n",
    "        name: str\n",
    "            name of section\n",
    "        item_dict : dictionary\n",
    "            dictionary of named (key) config values (value)\n",
    "        \"\"\"\n",
    "        self.name = name\n",
    "        for key, value in item_dict.items():\n",
    "            setattr(self, key, value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we can rewrite our `Config` class this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Config:\n",
    "    def __init__(self, env='dev'):\n",
    "        print(f'Loading config from {env} file...')\n",
    "        config = configparser.ConfigParser()\n",
    "        file_name = f'{env}.ini'\n",
    "        config.read(file_name)\n",
    "        for section_name in config.sections():\n",
    "            section = Section(section_name, config[section_name])\n",
    "            setattr(self, section_name.lower(), section)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loading config from dev file...\n"
     ]
    }
   ],
   "source": [
    "config = Config()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we have sections:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'database': <__main__.Section at 0x7f8ce09f6e48>,\n",
       " 'server': <__main__.Section at 0x7f8ce09f65f8>}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And each section has its config values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'Database', 'db_host': 'dev.mynetwork.com', 'db_name': 'my_database'}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(config.database)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But that still does not solve our documentation issue:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Config in module __main__:\n",
      "\n",
      "class Config(builtins.object)\n",
      " |  Config(env='dev')\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, env='dev')\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Most modern IDE's will still be able to provide us some auto-completion on the attributes though, using some form of introspection."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But let's assume we really want `help` to give us some useful information, or we're working with an IDE that isn't sophisticated enough."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To do that, we are going to switch to metaclasses."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our custom metaclass will load up the `ini` file and use it to create class attributes instead:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we'll need to do this for both the sections and the overall config."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To keep things a little simpler, we're going to create two distinct metaclasses. One for the sections in the config file, and one that combines the sections together - very similar to what we did with our original `Config` class."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One key difference, is that each `Section` class instance, will be a brand new class, created via its metaclass."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's write the `Section` metaclass first."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SectionType(type):\n",
    "    def __new__(cls, name, bases, cls_dict, section_name, items_dict):\n",
    "        cls_dict['__doc__'] = f'Configs for {section_name} section'\n",
    "        cls_dict['section_name'] = section_name\n",
    "        for key, value in items_dict.items():\n",
    "            cls_dict[key] = value\n",
    "        return super().__new__(cls, name, bases, cls_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now create `Section` classes for different sections in our configs, passing the metaclass the section name, and a dictionary of the values it should create as class attributes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DatabaseSection(metaclass=SectionType, section_name='database', items_dict={'db_name': 'my_database', 'host': 'myhost.com'}):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__doc__': 'Configs for database section',\n",
       "              'section_name': 'database',\n",
       "              'db_name': 'my_database',\n",
       "              'host': 'myhost.com',\n",
       "              '__dict__': <attribute '__dict__' of 'DatabaseSection' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'DatabaseSection' objects>})"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(DatabaseSection)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, our items `db_name` and `host` are in the class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'my_database'"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "DatabaseSection.db_name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And the `help` function introspection will work too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class DatabaseSection in module __main__:\n",
      "\n",
      "class DatabaseSection(builtins.object)\n",
      " |  Configs for database section\n",
      " |  \n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data and other attributes defined here:\n",
      " |  \n",
      " |  db_name = 'my_database'\n",
      " |  \n",
      " |  host = 'myhost.com'\n",
      " |  \n",
      " |  section_name = 'database'\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(DatabaseSection)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can now create any section we want using this metaclass, for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PasswordsSection(metaclass=SectionType, section_name='passwords', items_dict={'db': 'secret', 'site': 'super secret'}):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__doc__': 'Configs for passwords section',\n",
       "              'section_name': 'passwords',\n",
       "              'db': 'secret',\n",
       "              'site': 'super secret',\n",
       "              '__dict__': <attribute '__dict__' of 'PasswordsSection' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'PasswordsSection' objects>})"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(PasswordsSection)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just like we can create classes programmatically by calling the `type` metaclass:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "MyClass = type('MyClass', (object,), {})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.MyClass"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MyClass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also create `Section` **classes** by calling the `SectionType` metaclass:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "MySection = SectionType('DBSection', (object,), {}, section_name='databases', items_dict={'db_name': 'my_db', 'port': 8000})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.DBSection"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MySection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__doc__': 'Configs for databases section',\n",
       "              'section_name': 'databases',\n",
       "              'db_name': 'my_db',\n",
       "              'port': 8000,\n",
       "              '__module__': '__main__',\n",
       "              '__dict__': <attribute '__dict__' of 'DBSection' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'DBSection' objects>})"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(MySection)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have a metaclass to create section classes, we can build our main config metaclass to build the `Config` class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ConfigType(type):\n",
    "    def __new__(cls, name, bases, cls_dict, env):\n",
    "        \"\"\"\n",
    "        env : str\n",
    "            The environment we are loading the config for (e.g. dev, prod)\n",
    "        \"\"\"\n",
    "        cls_dict['__doc__'] = f'Configurations for {env}.'\n",
    "        cls_dict['env'] = env\n",
    "        config = configparser.ConfigParser()\n",
    "        file_name = f'{env}.ini'\n",
    "        config.read(file_name)\n",
    "        for section_name in config.sections():\n",
    "            class_name = section_name.capitalize()\n",
    "            class_attribute_name = section_name.casefold()\n",
    "            section_items = config[section_name]\n",
    "            bases = (object, )\n",
    "            section_cls_dict = {}\n",
    "            # create a new Section class for this section\n",
    "            Section = SectionType(\n",
    "                class_name, bases, section_cls_dict, section_name=section_name, items_dict=section_items\n",
    "            )\n",
    "            # And assign it to an attribute in the main config class\n",
    "            cls_dict[class_attribute_name] = Section\n",
    "        return super().__new__(cls, name, bases, cls_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can create config classes for each of our environments:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DevConfig(metaclass=ConfigType, env='dev'):\n",
    "    pass\n",
    "\n",
    "class ProdConfig(metaclass=ConfigType, env='prod'):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__doc__': 'Configurations for dev.',\n",
       "              'env': 'dev',\n",
       "              'database': __main__.Database,\n",
       "              'server': __main__.Server,\n",
       "              '__dict__': <attribute '__dict__' of 'DevConfig' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'DevConfig' objects>})"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(DevConfig)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class DevConfig in module __main__:\n",
      "\n",
      "class DevConfig(builtins.object)\n",
      " |  Configurations for dev.\n",
      " |  \n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data and other attributes defined here:\n",
      " |  \n",
      " |  database = <class '__main__.Database'>\n",
      " |      Configs for Database section\n",
      " |  \n",
      " |  env = 'dev'\n",
      " |  \n",
      " |  server = <class '__main__.Server'>\n",
      " |      Configs for Server section\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(DevConfig)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__doc__': 'Configs for Database section',\n",
       "              'section_name': 'Database',\n",
       "              'db_host': 'dev.mynetwork.com',\n",
       "              'db_name': 'my_database',\n",
       "              '__module__': '__main__',\n",
       "              '__dict__': <attribute '__dict__' of 'Database' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Database' objects>})"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(DevConfig.database)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Database in module __main__:\n",
      "\n",
      "class Database(builtins.object)\n",
      " |  Configs for Database section\n",
      " |  \n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data and other attributes defined here:\n",
      " |  \n",
      " |  db_host = 'dev.mynetwork.com'\n",
      " |  \n",
      " |  db_name = 'my_database'\n",
      " |  \n",
      " |  section_name = 'Database'\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(DevConfig.database)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('dev.mynetwork.com', 'prod.mynetwork.com')"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "DevConfig.database.db_host, ProdConfig.database.db_host"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
